﻿// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

// ReSharper disable CheckNamespace
// ReSharper disable CommentTypo
// ReSharper disable IdentifierTypo
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global

/* PftVariableReference.cs -- ссылка на переменную в PFT
 * Ars Magna project, http://arsmagna.ru
 */

#region Using directives

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

using AM;
using AM.IO;

using ManagedIrbis.Pft.Infrastructure.Compiler;
using ManagedIrbis.Pft.Infrastructure.Diagnostics;
using ManagedIrbis.Pft.Infrastructure.Serialization;
using ManagedIrbis.Pft.Infrastructure.Text;

using Microsoft.Extensions.Logging;

#endregion

#nullable enable

namespace ManagedIrbis.Pft.Infrastructure.Ast;

/// <summary>
/// Ссылка на переменную в PFT.
/// </summary>
public sealed class PftVariableReference
    : PftNumeric
{
    #region Properties

    /// <summary>
    /// Имя переменной.
    /// </summary>
    public string? Name { get; set; }

    /// <summary>
    /// Индекс.
    /// </summary>
    public IndexSpecification Index { get; set; }

    /// <summary>
    /// Код подполя.
    /// </summary>
    public char SubFieldCode { get; set; }

    /// <inheritdoc cref="PftNode.ExtendedSyntax" />
    public override bool ExtendedSyntax => true;

    #endregion

    #region Construction

    /// <summary>
    /// Конструктор по умолчанию.
    /// </summary>
    public PftVariableReference()
    {
        // пустое тело конструктора
    }

    /// <summary>
    /// Конструктор.
    /// </summary>
    public PftVariableReference
        (
            string name
        )
    {
        Sure.NotNullNorEmpty (name);

        Name = name;
    }

    /// <summary>
    /// Конструктор.
    /// </summary>
    public PftVariableReference
        (
            string name,
            int index
        )
    {
        Sure.NotNullNorEmpty (name);
        Sure.NonNegative (index);

        Name = name;
        Index = IndexSpecification.GetLiteral (index);
    }

    /// <summary>
    /// Конструктор.
    /// </summary>
    public PftVariableReference
        (
            string name,
            char code
        )
    {
        Sure.NotNullNorEmpty (name);

        Name = name;
        SubFieldCode = code;
    }

    /// <summary>
    /// Конструктор.
    /// </summary>
    public PftVariableReference
        (
            PftToken token
        )
        : base (token)
    {
        Sure.NotNull (token);

        token.MustBe (PftTokenKind.Variable);

        var text = token.Text.ThrowIfNull();
        if (text.StartsWith ("$"))
        {
            text = text.Substring (1);
        }

        Name = text;
    }

    #endregion

    #region Private members

    private static ReadOnlyMemory<char> _ReadTo
        (
            TextReader reader,
            char delimiter
        )
    {
        var result = new StringBuilder();

        while (true)
        {
            var next = reader.Read();
            if (next < 0)
            {
                break;
            }

            var c = (char)next;
            if (c == delimiter)
            {
                break;
            }

            result.Append (c);
        }

        return result.ToString().AsMemory();
    }

    private static Field _ParseLine
        (
            string line
        )
    {
        var reader = new StringReader (line);
        var result = new Field
        {
            Value = _ReadTo (reader, '^').ToString()
        };

        while (true)
        {
            var next = reader.Read();
            if (next < 0)
            {
                break;
            }

            var code = char.ToLower ((char)next);
            var text = _ReadTo (reader, '^').ToString();
            var subField = new SubField
            {
                Code = code,
                Value = text
            };
            result.Subfields.Add (subField);
        }

        return result;
    }

    #endregion

    #region Public methods

    /// <summary>
    /// Do the variable.
    /// </summary>
    public static double DoVariable
        (
            PftContext context,
            PftNode node,
            string name,
            IndexSpecification index,
            char subField
        )
    {
        Sure.NotNull (context);
        Sure.NotNull (node);
        Sure.NotNullNorEmpty (name);

        var result = 0.0;

        var variable = context.Variables.GetExistingVariable (name);
        if (ReferenceEquals (variable, null))
        {
            Magna.Logger.LogError
                (
                    nameof (PftVariableReference) + "::" + nameof (DoVariable)
                    + ": unknown variable {Name}",
                    name.ToVisibleString()
                );

            throw new PftSemanticException
                (
                    "unknown variable: " + name
                );
        }

        if (variable.IsNumeric)
        {
            result = variable.NumericValue;
            context.Write
                (
                    node,
                    result.ToInvariantString()
                );
        }
        else
        {
            var output = variable.StringValue ?? string.Empty;

            if (index.Kind != IndexKind.None)
            {
                var lines = output.SplitLines();

                lines = PftUtility.GetArrayItem
                    (
                        context,
                        lines,
                        index
                    );

                if (subField != SubField.NoCode)
                {
                    var list = new List<string>();

                    foreach (var line in lines)
                    {
                        var field = _ParseLine (line);
                        var text = field.GetFirstSubFieldValue
                            (
                                subField
                            );
                        if (!text.IsEmpty())
                        {
                            list.Add (text);
                        }
                    }

                    lines = list.ToArray();
                }

                output = string.Join
                    (
                        Environment.NewLine,
                        lines
                    );
            }
            else
            {
                if (subField != SubField.NoCode)
                {
                    var lines = output.SplitLines();

                    var list = new List<string>();

                    foreach (var line in lines)
                    {
                        var field = _ParseLine (line);
                        var text = field.GetFirstSubFieldValue
                            (
                                subField
                            );
                        if (!text.IsEmpty())
                        {
                            list.Add (text);
                        }
                    }

                    lines = list.ToArray();
                    output = string.Join
                        (
                            Environment.NewLine,
                            lines
                        );
                }
            }

            context.Write (node, output);
        }

        return result;
    }

    #endregion

    #region ICloneable members

    /// <inheritdoc cref="ICloneable.Clone" />
    public override object Clone()
    {
        var result
            = (PftVariableReference)base.Clone();

        result.Index = (IndexSpecification)Index.Clone();

        return result;
    }

    #endregion

    #region PftNode members

    /// <inheritdoc cref="PftNode.CompareNode" />
    internal override void CompareNode
        (
            PftNode otherNode
        )
    {
        base.CompareNode (otherNode);

        var otherVariable
            = (PftVariableReference)otherNode;
        if (Name != otherVariable.Name
            || !IndexSpecification.Compare (Index, otherVariable.Index)
            || SubFieldCode != otherVariable.SubFieldCode)
        {
            throw new PftSerializationException();
        }
    }

    /// <inheritdoc cref="PftNode.Compile" />
    public override void Compile
        (
            PftCompiler compiler
        )
    {
        if (ReferenceEquals (Name, null))
        {
            throw new PftCompilerException();
        }

        var index = compiler.CompileIndex (Index);

        compiler.StartMethod (this);

        compiler
            .WriteIndent()
            .WriteLine
                (
                    "double result = PftVariableReference.DoVariable("
                    + "Context, null, \"{0}\", {1}, '\\x{2:X4}');",
                    CompilerUtility.Escape (Name),
                    index.Reference,
                    (int)SubFieldCode
                )
            .WriteIndent()
            .WriteLine ("return result;");

        compiler.EndMethod (this);
        compiler.MarkReady (this);
    }

    /// <inheritdoc cref="PftNode.Deserialize" />
    protected internal override void Deserialize
        (
            BinaryReader reader
        )
    {
        base.Deserialize (reader);

        Name = reader.ReadNullableString();
        Index.Deserialize (reader);
        SubFieldCode = reader.ReadChar();
    }

    /// <inheritdoc cref="PftNode.Execute" />
    public override void Execute
        (
            PftContext context
        )
    {
        OnBeforeExecution (context);

        Value = DoVariable
            (
                context,
                this,
                Name.ThrowIfNull(),
                Index,
                SubFieldCode
            );

        OnAfterExecution (context);
    }

    /// <inheritdoc cref="PftNode.GetNodeInfo" />
    public override PftNodeInfo GetNodeInfo()
    {
        var result = base.GetNodeInfo();

        if (Index.Kind != IndexKind.None)
        {
            result.Children.Add (Index.GetNodeInfo());
        }

        return result;
    }

    /// <inheritdoc cref="PftNode.PrettyPrint" />
    public override void PrettyPrint
        (
            PftPrettyPrinter printer
        )
    {
        printer.Write (ToString());
    }

    /// <inheritdoc cref="PftNode.Serialize" />
    protected internal override void Serialize
        (
            BinaryWriter writer
        )
    {
        base.Serialize (writer);

        writer.WriteNullable (Name);
        Index.Serialize (writer);
        writer.Write (SubFieldCode);
    }

    /// <inheritdoc cref="PftNode.ShouldSerializeText" />
    protected internal override bool ShouldSerializeText() => false;

    #endregion

    #region Object members

    /// <inheritdoc cref="object.ToString" />
    public override string ToString()
    {
        var result = new StringBuilder();

        result.Append ("$");
        result.Append (Name);
        if (Index.Kind != IndexKind.None)
        {
            result.Append (Index.ToText());
        }

        if (SubFieldCode != SubField.NoCode)
        {
            result.Append ('^');
            result.Append (SubFieldCode);
        }


        return result.ToString();
    }

    #endregion
}
